﻿using System.Collections.Generic;
using Microsoft.Maui.Graphics.Text.Immutable;
using System.IO;
using System.Linq;
using System;

namespace Microsoft.Maui.Graphics.Text
{
    public static class AttributedTextExtensions
    {
        public static IAttributedText Optimize(this IAttributedText attributedText)
        {
            if (attributedText?.Text == null)
                return null;

            if (attributedText is AbstractAttributedText abstractAttributedText && abstractAttributedText.Optimal)
                return attributedText;

            var start = 0;
            var attributeIndex = 0;
            var text = attributedText.Text;
            var length = text.Length;
            var runs = new List<IAttributedTextRun>();
            attributedText.CreateParagraphRun(start, length, runs, attributeIndex);
            return new AttributedText(text, runs, true);
        }

        internal static List<IAttributedTextRun> OptimizeRuns(this IAttributedText attributedText)
        {
            if (attributedText?.Text == null)
                return null;

            if (attributedText is AbstractAttributedText abstractAttributedText && abstractAttributedText.Optimal)
            {
                if (attributedText.Runs == null)
                    return null;

                if (attributedText.Runs is List<IAttributedTextRun> list)
                    return list;

                return attributedText.Runs.ToList();
            }

            var start = 0;
            var attributeIndex = 0;
            var text = attributedText.Text;
            var length = text.Length;
            var runs = new List<IAttributedTextRun>();
            attributedText.CreateParagraphRun(start, length, runs, attributeIndex);
            return runs;
        }

        public static IReadOnlyList<IAttributedText> CreateParagraphs(this IAttributedText attributedText)
        {
            if (attributedText?.Text == null)
                return null;

            List<IAttributedText> paragraphs = new List<IAttributedText>();

            int start = 0;
            int attributeIndex = 0;

            using (var sr = new StringReader(attributedText.Text))
            {
                string line;
                while ((line = sr.ReadLine()) != null)
                {
                    var length = line.Length;

                    var runs = new List<IAttributedTextRun>();
                    attributeIndex = attributedText.CreateParagraphRun(start, length, runs, attributeIndex);

                    var paragraph = new AttributedText(line, runs);
                    paragraphs.Add(paragraph);

                    start += length + 1;
                }
            }

            return paragraphs;
        }

        public static int CreateParagraphRun(
            this IAttributedText text,
            int start,
            int length,
            IList<IAttributedTextRun> runs,
            int startIndexForSearch = 0)
        {
            // If the text doesn't have any runs, then we can simply return
            if (text.Runs == null || text.Runs.Count == 0)
                return 0;

            // If we've already reached the end of the runs, we can simply return
            if (!(startIndexForSearch < text.Runs.Count))
                return startIndexForSearch;

            var end = start + length;
            var index = startIndexForSearch;

            do
            {
                var run = text.Runs[index];

                // If the run is after the end index, then we can go ahead and return
                if (end < run.Start)
                    return index;

                if (run.Intersects(start, length))
                {
                    if (start == run.Start)
                    {
                        var paragraphStart = run.Start - start;
                        var paragraphLength = Math.Min(run.Length, length);
                        runs.Add(new AttributedTextRun(paragraphStart, paragraphLength, run.Attributes));

                        // If the length of the run is the same as the paragraph, then we know
                        // that the next run (if any) will apply to to the next paragraph.
                        if (run.Length == length)
                            return index + 1;

                        // If the run is longer than the line, then we know that the attributes from this run
                        // will also apply to the next paragraph.
                        if (run.Length > length)
                            return index;

                        // If the run length is less than the length of the line, then the next run may apply
                        // to this line, so continue
                    }
                    else if (end == run.GetEnd())
                    {
                        var paragraphStart = Math.Max(run.Start - start, 0);
                        var paragraphLength = Math.Min(run.Length, end - paragraphStart);
                        runs.Add(new AttributedTextRun(paragraphStart, paragraphLength, run.Attributes));

                        // Since the run and the line have the same end, we know that the next run (if any) will 
                        // apply to to the next paragraph.
                        return index + 1;
                    }
                    else
                    {
                        var paragraphStart = Math.Max(run.Start - start, 0);
                        var paragraphLength = Math.Min(run.Length, length - paragraphStart);
                        runs.Add(new AttributedTextRun(paragraphStart, paragraphLength, run.Attributes));
                    }
                }

                index++;
            } while (index < text.Runs.Count);

            return index;
        }

        public static IList<AttributedTextBlock> CreateBlocks(this IAttributedText text)
        {
            if (text?.Text == null)
                return null;

            var blocks = new List<AttributedTextBlock>();

            var start = 0;
            var end = text.Text.Length;

            if (text.Runs?.Count > 0)
            {
                foreach (var run in text.Runs)
                {
                    if (start < run.Start)
                    {
                        var noAttrLength = run.Start - start;
                        var noAttrValue = text.Text.Substring(start, noAttrLength);
                        blocks.Add(new AttributedTextBlock(noAttrValue, null));
                        start = run.Start;
                    }

                    var length = run.Length;
                    if (length > 0)
                    {
                        var value = text.Text.Substring(start, length);
                        blocks.Add(new AttributedTextBlock(value, run.Attributes));
                        start = run.GetEnd();
                    }
#if DEBUG
                    else
                        System.Diagnostics.Debug.WriteLine("Length should not be less then 0");
#endif
                }
            }

            if (start < end)
            {
                var value = text.Text.Substring(start);
                blocks.Add(new AttributedTextBlock(value, null));
            }

            return blocks;
        }
    }
}